package com.leo.spring.api;

import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import javax.validation.constraints.*;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;

/**
 * 用于捕获项目中所有的接口，对于List中的泛型以及继承父类数据模型的属性问题都能实现识别，不支持入口参数直接是LIST的，必须是标准的请求模型和响应模型
 */

public class CatchApi implements CommandLineRunner {
    private static final Logger logger = LoggerFactory.getLogger(CatchApi.class);

    //@Autowired
    private ApplicationContext context;

    /**
     * 数据存储区域
     */
    private List<Class> apiServices;

    /**
     * 非空参数注解列表
     */
    private List<Class> notNulls;

    private List<Class> baseModel;
    /**
     * 接口模型存储
     */
    private List<API> apis;

    /**
     * 项目总包名，用于识别是否是项目本身的数据模型
     */
    private String packageName;

    {
        apiServices = new ArrayList<>();

        apis = new ArrayList<>();

        notNulls = new ArrayList<>();
        notNulls.add(NotNull.class);
        notNulls.add(NotEmpty.class);
        notNulls.add(Max.class);
        notNulls.add(Min.class);
        notNulls.add(DecimalMax.class);
        notNulls.add(DecimalMin.class);

        baseModel = new ArrayList<>();
        baseModel.add(Integer.class);
        baseModel.add(Long.class);
        baseModel.add(Short.class);
        baseModel.add(Byte.class);
        baseModel.add(String.class);
        baseModel.add(Float.class);
        baseModel.add(Double.class);
        baseModel.add(Character.class);
        baseModel.add(Boolean.class);

        String name = this.getClass().getName();
        packageName = name.substring(0, name.lastIndexOf("."));
    }

    @Override
    public void run(String... argas) throws Exception {
        initApiService();
        initAPI();
        //输出格式化文档
        BufferedWriter writer = new BufferedWriter(new FileWriter(new File("./api_doc.txt")));
        for (API api: apis) {
            writer.write("接口名称: " + api.getName() + "\n");
            writer.write("接口版本: " + api.getVersion() + "\n");
            writer.write("接口功能描述: " + api.getDescription() + "\n");

            if (api.getParameters() != null) {
                writer.write("接口参数: " + "\n");
                outParameter(api.getParameters(), 1, writer);
                writer.write("简易demo: " + api.getDemoReqJson() + "\n");
                writer.write("YApi导入: " + this.toYApi(api.getParameters()) + "\n");
            }

            if (api.getRespParameters() != null) {
                writer.write("接口响应模型: " + "\n");
                outParameter(api.getRespParameters(), 1, writer);
                writer.write("简易demo: " + api.getDemoRespJson() + "\n");
                writer.write("YApi导入: " + this.toYApi(api.getRespParameters()) + "\n");
            } else {
                writer.write("接口返回值类型: " + api.getRespTypeName());
            }

            writer.write("\n\n\n");
        }
        writer.close();
    }

    /**
     * 格式化的参数输出
     */
    private void outParameter(List<MyParameter> myParameters, int index, BufferedWriter writer) throws IOException {
        for (MyParameter temp: myParameters) {
            for (int i=0; i<index; i++) {
                writer.write("\t");
            }
            writer.write("名称: " + temp.getName() + "\t" + "是否必需: " + temp.getNecessary() + "\t" + "参数类型: " + temp.getType() + "\t" + "参数含义: " + temp.getDescription() + "\t");
            if (temp.getSonParameters() != null && temp.getSonParameters().size() > 0) {
                writer.write("有子参数列表\n");
                outParameter(temp.getSonParameters(), index + 1, writer);
            } else {
                writer.write("\n");
            }
        }
    }

    /**
     * 加载所有ApiService
     */
    public void initApiService() {
        String[] beanNames = context.getBeanDefinitionNames();
        for(String temp: beanNames) {
            //获取bean对象
            Object bean = context.getBean(temp);
            //获取类模版
            Class clazz = bean.getClass();

            Annotation[] annotations = clazz.getAnnotations();
            boolean flag = false;
            for(Annotation annotation: annotations) {
                if(annotation.annotationType().equals(ApiService.class)) {
                    apiServices.add(clazz);
                    flag = true;
                    break;
                }
            }
            if (!flag) {
                //判断类模板上是否存在@ApiService注解，兼容动态代理
                Annotation[] superAnnotations = clazz.getSuperclass().getAnnotations();
                for(Annotation annotation: superAnnotations) {
                    if(annotation.annotationType().equals(ApiService.class)) {
                        apiServices.add(clazz.getSuperclass());
                        break;
                    }
                }
            }
        }
    }

    /**
     * 加载所有的接口模型
     */
    private void initAPI() {
        //轮询ApiService
        for (Class temp: apiServices) {
            //获取该service下提供的所有接口
            Method[] methods = temp.getMethods();
            //轮询所有方法，判断是否为接口
            for (Method method: methods) {
                //判断方法模板上是否存在@Api注解
                Annotation[] annotations = method.getAnnotations();
                for (Annotation annotation: annotations) {
                    if(annotation.annotationType().equals(Api.class)) {
                        //读取该注解的信息
                        try {
                            Map<String, Object> message = this.getMsgFromAnnotation(annotation);
                            String name = (String) message.get("name");
                            String version = (String) message.get("version");
                            String description = null;
                            //获取接口功能信息
                            for (Annotation temp1: annotations) {
                                if(temp1.annotationType().equals(ApiDocMethod.class)) {
                                    //解析注解，获取信息
                                    Map<String, Object> msg = this.getMsgFromAnnotation(temp1);
                                    description = msg.get("description")!=null? (String) msg.get("description") :"";
                                }
                            }
                            //创建接口数据模型
                            API api = new API();
                            api.setName(name);
                            api.setVersion(version);
                            api.setDescription(description);
                            api.setMethod(method);
                            apis.add(api);
                            //处理接口的参数
                            this.handleParameter(api);
                            //根据接口的请求响应模型参数，生成一个简易的demo
                            if (api.getParameters() != null && api.getParameters().size() > 0) {
                                api.setDemoReqJson(this.toJSONString(api.getParameters()));
                            } else {
                                api.setDemoReqJson("");
                            }
                            if (api.getRespParameters() != null && api.getRespParameters().size() > 0) {
                                api.setDemoRespJson(this.toJSONString(api.getRespParameters()));
                            } else {
                                api.setDemoRespJson("");
                            }
                        } catch (InvocationTargetException e) {
                            e.printStackTrace();
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                        break;
                    }
                }
            }
        }
    }

    /**
     * 处理单个接口的参数信息
     */
    private void handleParameter(API api) {
        Method method = api.getMethod();
        //获取参数模板
        Parameter[] parameters = method.getParameters();
        //easyopen规定的只能有一个请求模型
        if (parameters[0].getName().contains("java.lang")) {
            return;
        }
        //获取请求模型类模板
        Class clazz = parameters[0].getType();
        List<MyParameter> myParameters = this.handleModel(clazz);
        api.setParameters(myParameters);
        //获取响应模型类模板
        Class clazzResp = method.getReturnType();
        if (!clazzResp.getSimpleName().equals("void") && !baseModel.contains(clazzResp)) {
            List<MyParameter> myResp = this.handleModel(clazzResp);
            api.setRespParameters(myResp);
        }
        api.setRespTypeName(clazzResp.getSimpleName());
    }

    /**
     * 处理数据模型
     */
    private List<MyParameter> handleModel(Class clazz) {
        List<MyParameter> myParameters = new ArrayList<>();
        //获取请求模型上的所有属性get方法，并根据get方法获取所有的属性
        Method[] getMethods = clazz.getDeclaredMethods();
        List<Field> fields = new ArrayList();
        for (Method tempMethod: getMethods) {
            String methodName = tempMethod.getName();
            if (methodName.startsWith("get") && methodName.length() > 3) {
                //进行名字转换
                String tempMethodName = methodName;
                methodName = methodName.substring(3);
                char character = methodName.charAt(0);
                methodName = methodName.substring(1);
                character = character <= 'Z' ? (char) (character + 32) : character;
                methodName = character + methodName;
                //根据名字获取属性
                try {
                    fields.add(clazz.getDeclaredField(methodName));
                } catch (NoSuchFieldException e) {
                    logger.info(tempMethodName + "无法匹配对应属性");
                    //尝试匹配大写
                    methodName = methodName.substring(1);
                    character = (char) (character - 32);
                    methodName = character + methodName;
                    try {
                        fields.add(clazz.getDeclaredField(methodName));
                    } catch (NoSuchFieldException noSuchFieldException) {
                        logger.info(tempMethodName + "无法匹配对应属性");
                        noSuchFieldException.printStackTrace();
                    }
                }
            }
        }

        //轮询所有属性
        for (Field tempField: fields) {
            //新建一个参数数据模型
            MyParameter myParameter = new MyParameter();
            myParameter.setParameter(tempField);
            //获取属性名称，三种方式：ApiDocField注解 --> JSONField注解 --> 默认名称
            Annotation[] annotations = tempField.getAnnotations();
            for (Annotation annotation: annotations) {
                //以ApiDocField为最高优先级
                if (annotation.annotationType().equals(ApiDocField.class)) {
                    try {
                        Map<String, Object> msg = this.getMsgFromAnnotation(annotation);
                        String description = msg.get("description")!=null? (String) msg.get("description") :"";
                        String name = (String) msg.get("name");
                        boolean required = (boolean) msg.get("required");
                        myParameter.setName(name);
                        myParameter.setDescription(description);
                        myParameter.setNecessary(required?"必需":"非必需");
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
                if (annotation.annotationType().equals(JSONField.class)) {
                    try {
                        Map<String, Object> msg = this.getMsgFromAnnotation(annotation);
                        String name = (String) msg.get("name");
                        if (myParameter.getName() == null || myParameter.getName().equals("")) {
                            myParameter.setName(name);
                        }
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
                if (annotation.annotationType().equals(JsonProperty.class)) {
                    try {
                        Map<String, Object> msg = this.getMsgFromAnnotation(annotation);
                        String name = (String) msg.get("value");
                        if (myParameter.getName() == null || myParameter.getName().equals("")) {
                            myParameter.setName(name);
                        }
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
            if (myParameter.getName() == null || myParameter.getName().equals("")) {
                myParameter.setName(tempField.getName());
            }
            if (myParameter.getDescription() == null) {
                myParameter.setDescription("");
            }
            if (myParameter.getNecessary() == null || myParameter.getNecessary().equals("")) {
                myParameter.setNecessary("非必需");
            }
            if ("非必需".equals(myParameter.getNecessary())) {
                for (Annotation annotation: annotations) {
                    if (notNulls.contains(annotation.annotationType())) {
                        myParameter.setNecessary("必需");
                        break;
                    }
                }
            }
            //填充类型
            myParameter.setType(tempField.getType().getSimpleName());
            myParameter.setTypeClass(tempField.getType());
            //泛型和数据模型嵌套的处理
            if (tempField.getType().equals(List.class) || Set.class.isAssignableFrom(tempField.getType()) || tempField.getType().equals(List.class) || Set.class.isAssignableFrom(tempField.getType())) {
                //List/Set集合
                //进行泛型获取，拆解泛型
                Type genericType = tempField.getGenericType();
                if (genericType != null && genericType instanceof ParameterizedType) {
                    ParameterizedType pt = (ParameterizedType) genericType;
                    // 得到泛型里的class类型对象
                    Class<?> actualTypeArgument = (Class<?>)pt.getActualTypeArguments()[0];
                    //把泛型也填入类型介绍
                    myParameter.setType(myParameter.getType() + "<" + actualTypeArgument.getSimpleName() + ">");
                    if (actualTypeArgument.getName().contains(packageName)) {
                        //如果是数据模型才需要继续拆解
                        myParameter.setSonParameters(this.handleModel(actualTypeArgument));
                    }
                }
            }else if (tempField.getType().getName().contains(packageName)){
                //说明类来自项目中，应该进行处理
                //处理数据模型嵌套
                myParameter.setType(tempField.getType().getSimpleName());
                myParameter.setTypeClass(tempField.getType());
                myParameter.setSonParameters(this.handleModel(tempField.getType()));
            }
            myParameters.add(myParameter);
        }
        //处理继承属性的问题
        List<MyParameter> tempList = this.getParentModel(clazz);
        if (tempList != null && tempList.size() > 0) {
            myParameters.addAll(this.getParentModel(clazz));
        }
        return myParameters;
    }

    /**
     * 处理父类模型的属性问题
     */
    private List<MyParameter> getParentModel(Class clazz) {
        List<MyParameter> parameters = null;
        Class parentClass = clazz.getSuperclass();
        if (parentClass != null && parentClass.getName().contains(packageName)) {
            //证明这个需要进行处理
            parameters = this.handleModel(parentClass);
        }
        return parameters;
    }

    /**
     * 根据parameter转成简易的JSON形式
     */
    private String toJSONString(List<MyParameter> parameters) {
        StringBuilder builder = new StringBuilder("{");
        for (MyParameter temp: parameters) {
            Class clazz = temp.getTypeClass();
            //拼接名称
            builder.append("\"");
            builder.append(temp.getName());
            builder.append("\": ");
            //对于不同类型，设置不同的值
            if (baseModel.contains(temp.getTypeClass())) {
                if (temp.getTypeClass().equals(Boolean.class)) {
                    builder.append("false");
                } else if (temp.getTypeClass().equals(String.class)) {
                    builder.append("\"\"");
                } else {
                    builder.append("0");
                }
            } else if (temp.getTypeClass().getName().contains(packageName)) {
                //证明这个是一个需要处理的自定义数据模型
                String jsonStr = this.toJSONString(temp.getSonParameters());
                builder.append(jsonStr);
            } else if (Collection.class.isAssignableFrom(temp.getTypeClass())) {
                builder.append("[");
                //证明这是一个集合
                if (temp.getSonParameters() != null && temp.getSonParameters().size() > 0) {
                    String jsonStr = this.toJSONString(temp.getSonParameters());
                    builder.append(jsonStr);
                }
                builder.append("]");
            } else {
                builder.append("{}");
            }
            builder.append(", ");
        }
        if (builder.length() > 1) {
            builder.delete(builder.length() - 2, builder.length());
        }
        builder.append("}");
        return builder.toString();
    }

    /**
     * YApi平台能识别的串的外包
     */
    private String toYApi(List<MyParameter> parameters) {
        StringBuilder builder = new StringBuilder("{" +
                "\"$schema\":\"http://json-schema.org/draft-04/schema#\"," +
                "\"type\":\"object\"," +
                "\"properties\":");
        builder.append(this.toJSON5String(parameters));
        //处理required的问题
        builder.append(this.getRequiredStr(parameters));
        builder.append("}");
        return builder.toString();
    }

    /**
     * JSON5格式化工具，用于ypai平台的导入
     */
    private String toJSON5String(List<MyParameter> parameters) {
        StringBuilder builder = new StringBuilder("{");
        for (MyParameter temp: parameters) {
            //拼接名称
            builder.append("\"");
            builder.append(temp.getName());
            builder.append("\": {\"type\": \"");
            //填充类型，各类型数据分别处理
            if (baseModel.contains(temp.getTypeClass())) {
                if (temp.getTypeClass().equals(Boolean.class)) {
                    //如果是boolean
                    builder.append("boolean");
                }else if (temp.getTypeClass().equals(String.class)) {
                    builder.append("string");
                }else {
                    builder.append("number");
                }
            } else if (Collection.class.isAssignableFrom(temp.getTypeClass())) {
                builder.append("array");
            } else {
                builder.append("object");
            }
            builder.append("\", \"description\": \"");

            //填充描述
            builder.append(temp.getDescription());
            builder.append("\"");

            //分情况实现拆解
            if (Collection.class.isAssignableFrom(temp.getTypeClass()) && temp.getSonParameters() != null && temp.getSonParameters().size() > 0) {
                builder.append(", \"items\": {\"type\": \"object\", \"properties\": ");
                builder.append(this.toJSON5String(temp.getSonParameters()));
                //处理required属性
                builder.append(this.getRequiredStr(temp.getSonParameters()));
                builder.append("}");
            } else if (temp.getSonParameters() != null && temp.getSonParameters().size() > 0) {
                builder.append(", \"properties\": ");
                builder.append(this.toJSON5String(temp.getSonParameters()));
                //处理required属性
                builder.append(this.getRequiredStr(temp.getSonParameters()));
            }
            builder.append("}, ");
        }
        if (builder.length() > 1) {
            builder.delete(builder.length() - 2, builder.length());
        }
        builder.append("}");
        return builder.toString();
    }

    /**
     * 拼接required字段
     */
    private String getRequiredStr(List<MyParameter> parameters) {
        //处理required属性
        StringBuilder builder = new StringBuilder(", \"required\": [");
        boolean flag = false;
        for (MyParameter parameter: parameters) {
            if ("必需".equals(parameter.getNecessary())) {
                if (flag) {
                    builder.append(", ");
                }
                builder.append("\"");
                builder.append(parameter.getName());
                builder.append("\"");
                flag = true;
            }
        }
        builder.append("]");
        return builder.toString();
    }

    /**
     * 注解解析工具
     */
    private Map<String, Object> getMsgFromAnnotation(Annotation annotation) throws InvocationTargetException, IllegalAccessException {
        Method[] methods = annotation.annotationType().getDeclaredMethods();
        Map<String, Object> message = new HashMap<>();
        for (Method method: methods) {
            method.setAccessible(true);
            Object value = method.invoke(annotation);
            message.put(method.getName(), value);
        }
        return message;
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    private static class API {
        private String name;
        private String version;
        private List<MyParameter> parameters;
        private String description;
        private Method method;
        private String demoReqJson;
        private List<MyParameter> respParameters;
        private String demoRespJson;
        private String respTypeName;
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    private static class MyParameter {
        private Field parameter;
        private String name;
        private String type;
        private Class typeClass;
        private String necessary;
        private String description;
        private List<MyParameter> sonParameters;
    }
}
